讲一个api网页全部保存到本地

您所在的位置:网站首页 nodejs 爬虫 上货提醒 讲一个api网页全部保存到本地

讲一个api网页全部保存到本地

2023-08-11 09:42| 来源: 网络整理| 查看: 265

a15490f04c3cd09bc0d4b9ae83bcb8c0.png

作者:徐小夕

转发链接:https://mp.weixin.qq.com/s/SiBF0xAb4lB-K4p4qsbwRA

前言

熟悉我的朋友可能会知道,我一向是不写热点的。为什么不写呢?是因为我不关注热点吗?其实也不是。有些事件我还是很关注的,也确实有不少想法和观点。但我一直奉行一个原则,就是:要做有生命力的内容。

本文介绍的内容来自于笔者之前负责研发的爬虫管理平台, 专门抽象出了一个相对独立的功能模块为大家讲解如何使用nodejs开发专属于自己的爬虫平台.文章涵盖的知识点比较多,包含nodejs, 爬虫框架, 父的进程及其通信, react和umi等知识, 笔者会以尽可能简单的语言向大家一一介绍.

你将收获 Apify框架介绍和基本使用如何创建父的进程以及父子进程通信使用javascript手动实现控制爬虫最大并发数截取整个网页图片的实现方案nodejs第三方库和模块的使用使用umi3 + antd4.0搭建爬虫前台界面 平台预览 49828e5ee2b9fa95059c4bbf1bff3aac.png 60c4ad7ea60aefff574058657eeb0220.png 0cf49b65b31dacee289533b637036a9f.png

上图所示的就是我们要实现的爬虫平台, 我们可以输入指定网址来抓取该网站下的数据,并生成整个网页的快照.在抓取完之后我们可以下载数据和图片.网页右边是用户抓取的记录,方便二次利用或者备份.

正文

在开始文章之前,我们有必要了解爬虫的一些应用. 我们一般了解的爬虫, 多用来爬取网页数据, 捕获请求信息, 网页截图等,如下图:

c36780e61c614d0bbe84403c90750330.png

当然爬虫的应用远远不止如此,我们还可以利用爬虫库做自动化测试, 服务端渲染, 自动化表单提交, 测试谷歌扩展程序, 性能诊断等. 任何语言实现的爬虫框架原理往往也大同小异, 接下来笔者将介绍基于nodejs实现的爬虫框架Apify以及用法,并通过一个实际的案例方便大家快速上手爬虫开发.

Apify框架介绍和基本使用

apify是一款用于JavaScript的可伸缩的web爬虫库。能通过无头(headless)Chrome 和 Puppeteer 实现数据提取和** Web** 自动化作业的开发。它提供了管理和自动扩展无头Chrome / Puppeteer实例池的工具,支持维护目标URL的请求队列,并可将爬取结果存储到本地文件系统或云端。

我们安装和使用它非常简单, 官网上也有非常多的实例案例可以参考, 具体安装使用步骤如下:

安装 npm install apify --save复制代码 使用Apify开始第一个案例 const Apify = require('apify');Apify.main(async () => { const requestQueue = await Apify.openRequestQueue(); await requestQueue.addRequest({ url: 'https://www.iana.org/' }); const pseudoUrls = [new Apify.PseudoUrl('https://www.iana.org/[.*]')]; const crawler = new Apify.PuppeteerCrawler({ requestQueue, handlePageFunction: async ({ request, page }) => { const title = await page.title(); console.log(`Title of ${request.url}: ${title}`); await Apify.utils.enqueueLinks({ page, selector: 'a', pseudoUrls, requestQueue, }); }, maxRequestsPerCrawl: 100, maxConcurrency: 10, }); await crawler.run();});复制代码

使用node执行后可能会出现如下界面:

ea411e9456a75d5a4ecb556116df67e4.png

程序会自动打开浏览器并打开满足条件的url页面. 我们还可以使用它提供的cli工具实现更加便捷的爬虫服务管理等功能,感兴趣的朋友可以尝试一下. apify提供了很多有用的api供开发者使用, 如果想实现更加复杂的能力,可以研究一下,下图是官网api截图:

e43685ec8c577720d8155ec83d593a0f.png

笔者要实现的爬虫主要使用了Apify集成的Puppeteer能力, 如果对Puppeteer不熟悉的可以去官网学习了解, 本文模块会一一列出项目使用的技术框架的文档地址.

如何创建父子进程以及父子进程通信

我们要想实现一个爬虫平台, 要考虑的一个关键问题就是爬虫任务的执行时机以及以何种方式执行. 因为爬取网页和截图需要等网页全部加载完成之后再处理, 这样才能保证数据的完整性, 所以我们可以认定它为一个耗时任务.

当我们使用nodejs作为后台服务器时, 由于nodejs本身是单线程的,所以当爬取请求传入nodejs时, nodejs不得不等待这个"耗时任务"完成才能进行其他请求的处理, 这样将会导致页面其他请求需要等待该任务执行结束才能继续进行, 所以为了更好的用户体验和流畅的响应,我们不得不考虑多进程处理. 好在nodejs设计支持子进程, 我们可以把爬虫这类耗时任务放入子进程中来处理,当子进程处理完成之后再通知主进程. 整个流程如下图所示:

1c1582f3997c7486da763eff910d5c21.png

nodejs有3种创建子进程的方式, 这里我们使用fork来处理, 具体实现方式如下:

// child.jsfunction computedTotal(arr, cb) { // 耗时计算任务}// 与主进程通信// 监听主进程信号process.on('message', (msg) => { computedTotal(bigDataArr, (flag) => { // 向主进程发送完成信号 process.send(flag); })});// main.jsconst { fork } = require('child_process');app.use(async (ctx, next) => { if(ctx.url === '/fetch') { const data = ctx.request.body; // 通知子进程开始执行任务,并传入数据 const res = await createPromisefork('./child.js', data) } // 创建异步线程 function createPromisefork(childUrl, data) { // 加载子进程 const res = fork(childUrl) // 通知子进程开始work data && res.send(data) return new Promise(reslove => { res.on('message', f => { reslove(f) }) }) } await next()})复制代码

以上是一个实现父子进程通信的简单案例, 我们的爬虫服务也会采用该模式来实现.

使用javascript手动实现控制爬虫最大并发数

以上介绍的是要实现我们的爬虫应用需要考虑的技术问题, 接下来我们开始正式实现业务功能, 因为爬虫任务是在子进程中进行的,所以我们将在子进程代码中实现我们的爬虫功能.我们先来整理一下具体业务需求, 如下图:

f3a1e27e7795331b25ba7c67b9884c1b.png

接下来我会先解决控制爬虫最大并发数这个问题, 之所以要解决这个问题, 是为了考虑爬虫性能问题, 我们不能一次性让爬虫爬取所以的网页,这样会开启很多并行进程来处理, 所以我们需要设计一个节流装置,来控制每次并发的数量, 当前一次的完成之后再进行下一次的页面抓取处理. 具体代码实现如下:

// 异步队列const queue = []// 最大并发数const max_parallel = 6// 开始指针let start = 0for(let i = 0; i < urls.length; i++) { // 添加异步队列 queue.push(fetchPage(browser, i, urls[i])) if(i && (i+1) % max_parallel === 0 || i === (urls.length - 1)) { // 每隔6条执行一次, 实现异步分流执行, 控制并发数 await Promise.all(queue.slice(start, i+1)) start = i }}复制代码

以上代码即可实现每次同时抓取6个网页, 当第一次任务都结束之后才会执行下一批任务.代码中的urls指的是用户输入的url集合, fetchPage为抓取页面的爬虫逻辑, 笔者将其封装成了promise.

如何截取整个网页快照

我们都知道puppeteer截取网页图片只会截取加载完成的部分,对于一般的静态网站来说完全没有问题, 但是对于页面内容比较多的内容型或者电商网站, 基本上都采用了按需加载的模式, 所以一般手段截取下来的只是一部分页面, 或者截取的是图片还没加载出来的占位符,如下图所示:

7f96333d4c77367885a830084098d7d1.png

所以为了实现截取整个网页,需要进行人为干预.笔者这里提供一种简单的实现思路, 可以解决该问题. 核心思路就是利用puppeteer的api手动让浏览器滚动到底部, 每次滚动一屏, 直到页面的滚动高度不变时则认为滚动到底部.具体实现如下:

// 滚动高度let scrollStep = 1080;// 最大滚动高度, 防止无限加载的页面导致长效耗时任务let max_height = 30000;let m = {prevScroll: -1, curScroll: 0}while (m.prevScroll !== m.curScroll && m.curScroll < max_height) { // 如果上一次滚动和本次滚动高度一样, 或者滚动高度大于设置的最高高度, 则停止截取 m = await page.evaluate((scrollStep) => { if (document.scrollingElement) { let prevScroll = document.scrollingElement.scrollTop; document.scrollingElement.scrollTop = prevScroll + scrollStep; let curScroll = document.scrollingElement.scrollTop return {prevScroll, curScroll} } }, scrollStep); // 等待3秒后继续滚动页面, 为了让页面加载充分 await sleep(3000);}// 其他业务代码...// 截取网页快照,并设置图片质量和保存路径const screenshot = await page.screenshot({path: `static/${uid}.jpg`, fullPage: true, quality: 70});复制代码

爬虫代码的其他部分因为不是核心重点,这里不一一举例, 我已经放到github上,大家可以交流研究.

有关如何提取网页文本, 也有现成的api可以调用, 大家可以选择适合自己业务的api去应用,笔者这里拿puppeteer的page.$eval来举例:

const txt = await page.$eval('body', el => { // el即为dom节点, 可以对body的子节点进行提取,分析 return {...}})复制代码 nodejs第三方库和模块的使用

为了搭建完整的node服务平台,笔者采用了

koa 一款轻量级可扩展node框架glob 使用强大的正则匹配模式遍历文件koa2-cors 处理访问跨域问题koa-static 创建静态服务目录koa-body 获取请求体数据 有关如何使用这些模块实现一个完整的服务端应用, 笔者在代码里做了详细的说明, 这里就不一一讨论了. 具体代码如下: const Koa = require('koa');const { resolve } = require('path');const staticServer = require('koa-static');const koaBody = require('koa-body');const cors = require('koa2-cors');const logger = require('koa-logger');const glob = require('glob');const { fork } = require('child_process');const app = new Koa();// 创建静态目录app.use(staticServer(resolve(__dirname, './static')));app.use(staticServer(resolve(__dirname, './db')));app.use(koaBody());app.use(logger());const config = { imgPath: resolve('./', 'static'), txtPath: resolve('./', 'db')}// 设置跨域app.use(cors({ origin: function (ctx) { if (ctx.url.indexOf('fetch') > -1) { return '*'; // 允许来自所有域名请求 } return ''; // 这样就能只允许 http://localhost 这个域名的请求了 }, exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'], maxAge: 5, // 该字段可选,用来指定本次预检请求的有效期,单位为秒 credentials: true, allowMethods: ['GET', 'POST', 'PUT', 'DELETE'], allowHeaders: ['Content-Type', 'Authorization', 'Accept', 'x-requested-with'],}))// 创建异步线程function createPromisefork(childUrl, data) { const res = fork(childUrl) data && res.send(data) return new Promise(reslove => { res.on('message', f => { reslove(f) }) })}app.use(async (ctx, next) => { if(ctx.url === '/fetch') { const data = ctx.request.body; const res = await createPromisefork('./child.js', data) // 获取文件路径 const txtUrls = []; let reg = /.*?(d+).w*$/; glob.sync(`${config.txtPath}/*.*`).forEach(item => { if(reg.test(item)) { txtUrls.push(item.replace(reg, '$1')) } }) ctx.body = { state: res, data: txtUrls, msg: res ? '抓取完成' : '抓取失败,原因可能是非法的url或者请求超时或者服务器内部错误' } } await next()})app.listen(80)复制代码 使用umi3 + antd4.0搭建爬虫前台界面

该爬虫平台的前端界面笔者采用umi3+antd4.0开发, 因为antd4.0相比之前版本确实体积和性能都提高了不少, 对于组件来说也做了更合理的拆分. 因为前端页面实现比较简单,整个前端代码使用hooks写不到200行,这里就不一一介绍了.

界面如下:

8564bf6be8ce4a7e943d6d96487e9123.png 推荐Vue学习资料文章:

《「实践」深入对比 Vue 3.0 Composition API 和 React Hooks》

《前端网红框架的插件机制全梳理(axios、koa、redux、vuex)》

《深入Vue 必学高阶组件 HOC「进阶篇」》

《深入学习Vue的data、computed、watch来实现最精简响应式系统》

《10个实例小练习,快速入门熟练 Vue3 核心新特性(一)》

《10个实例小练习,快速入门熟练 Vue3 核心新特性(二)》

《教你部署搭建一个Vue-cli4+Webpack移动端框架「实践」》

《2020前端就业Vue框架篇「实践」》

《详解Vue3中 router 带来了哪些变化?》

《Vue项目部署及性能优化指导篇「实践」》

《Vue高性能渲染大数据Tree组件「实践」》

《尤大大细品VuePress搭建技术网站与个人博客「实践」》

《10个Vue开发技巧「实践」》

《是什么导致尤大大选择放弃Webpack?【vite 原理解析】》

《带你了解 vue-next(Vue 3.0)之 小试牛刀【实践】》

《带你了解 vue-next(Vue 3.0)之 初入茅庐【实践】》

《实践Vue 3.0做JSX(TSX)风格的组件开发》

《一篇文章教你并列比较React.js和Vue.js的语法【实践】》

《手拉手带你开启Vue3世界的鬼斧神工【实践】》

《深入浅出通过vue-cli3构建一个SSR应用程序【实践】》

《怎样为你的 Vue.js 单页应用提速》

《聊聊昨晚尤雨溪现场针对Vue3.0 Beta版本新特性知识点汇总》

《【新消息】Vue 3.0 Beta 版本发布,你还学的动么?》

《Vue真是太好了 壹万多字的Vue知识点 超详细!》

《Vue + Koa从零打造一个H5页面可视化编辑器——Quark-h5》

《深入浅出Vue3 跟着尤雨溪学 TypeScript 之 Ref 【实践】》

《手把手教你深入浅出vue-cli3升级vue-cli4的方法》

《Vue 3.0 Beta 和React 开发者分别杠上了》

《手把手教你用vue drag chart 实现一个可以拖动 / 缩放的图表组件》

《Vue3 尝鲜》

《总结Vue组件的通信》

《手把手让你成为更好的Vue.js开发人员的12个技巧和窍门【实践】》

《Vue 开源项目 TOP45》

《2020 年,Vue 受欢迎程度是否会超过 React?》

《尤雨溪:Vue 3.0的设计原则》

《使用vue实现HTML页面生成图片》

《实现全栈收银系统(Node+Vue)(上)》

《实现全栈收银系统(Node+Vue)(下)》

《vue引入原生高德地图》

《Vue合理配置WebSocket并实现群聊》

《多年vue项目实战经验汇总》

《vue之将echart封装为组件》

《基于 Vue 的两层吸顶踩坑总结》

《Vue插件总结【前端开发必备】》

《Vue 开发必须知道的 36 个技巧【近1W字】》

《构建大型 Vue.js 项目的10条建议》

《深入理解vue中的slot与slot-scope》

《手把手教你Vue解析pdf(base64)转图片【实践】》

《使用vue+node搭建前端异常监控系统》

《推荐 8 个漂亮的 vue.js 进度条组件》

《基于Vue实现拖拽升级(九宫格拖拽)》

《手摸手,带你用vue撸后台 系列二(登录权限篇)》

《手摸手,带你用vue撸后台 系列三(实战篇)》

《前端框架用vue还是react?清晰对比两者差异》

《Vue组件间通信几种方式,你用哪种?【实践】》

《浅析 React / Vue 跨端渲染原理与实现》

《10个Vue开发技巧助力成为更好的工程师》

《手把手教你Vue之父子组件间通信实践讲解【props、$ref 、$emit】》

《1W字长文+多图,带你了解vue的双向数据绑定源码实现》

《深入浅出Vue3 的响应式和以前的区别到底在哪里?【实践】》

《干货满满!如何优雅简洁地实现时钟翻牌器(支持JS/Vue/React)》

《基于Vue/VueRouter/Vuex/Axios登录路由和接口级拦截原理与实现》

《手把手教你D3.js 实现数据可视化极速上手到Vue应用》

《吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【上】》

《吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【中】》

《吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【下】》

《Vue3.0权限管理实现流程【实践】》

《后台管理系统,前端Vue根据角色动态设置菜单栏和路由》

作者:徐小夕

转发链接:https://mp.weixin.qq.com/s/SiBF0xAb4lB-K4p4qsbwRA



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3